"""--------------------------------------------------------------------------------------------------
Filename:   mainapp.py
Author:     Chris Chester (christopher.chester@pg.canterbury.ac.nz) , adapted from work done by Ben Han (uha10@uclive.ac.nz)
Date:       17 April 2013
Description:
    (BI)ofeedback (S)trength & (SKI)ll (T)rainer - BISSKIT
    Biofeedback application for strength/skill training in Dysphagic patients.
    Main application module.
--------------------------------------------------------------------------------------------------"""

import sys, traceback
import wx
import wx.lib.newevent
import database
import numpy as np
from liveplot import LivePlot
from viewplot import ViewPlot
from controls import ControlPanel
from dialogs import BreakDialog, StartDialog
from config import AppState, SessionState
from mainframe import MainFrame
from handlers import MainHandlers, PlotHandlers, ReviewHandlers
from threading import RLock, Thread, Event
import Queue
import gc


# GUI id tags
ID_EXPORT_PATIENT = 51
ID_EXPORT_SESSION = 52
ID_CONFIGURATION = 53
ID_NEW_SESSION = 54
ID_STRENGTH = 1
ID_SKILL = 3


#===============================================================================
# CLASS: MainApp(wx.App)
#    Main application class for the biofeedback application.
#    Maintains ownership of the higher level objects during program execution
#===============================================================================
class MainApp(wx.App):
    def OnInit(self):
        wx.InitAllImageHandlers()
        print '==========================================================='
        print '    Main application started at:', wx.DateTime.Now().Format()
        print '==========================================================='
        
        # Display splash screen
        splash = StartDialog()
        splash.Show()
        
        self.status = AppState()                # state information for the application
        self.hdls = MainHandlers(self)          # event handlers for main window
        
        # Create the main frame
        self.main_frame = MainFrame(self, None, -1)
        self.SetTopWindow(self.main_frame)
        self.main_frame.Centre()
        self.main_frame.Show()
        
        self.hdls.frame = self.main_frame   # Pass the handler object a pointer to the main frame
        self.ctrl = None                    # Place holder for control panel window
        self.plot = None                    # Place holder for the live plot window
        self.console = None                 # Place holder for the serial Console object

        
        # Thread control objects
        self.pat_lock = RLock()             # must be acquired when modifying app.status.patient object
        self.sess_lock = RLock()            # must be acquired when modifying app.status.session object
        self.data_lock = RLock()            # must be acquired when modifying app.plot.x_data, app.plot.y_data arrays
        self.serial_loop_closed = Event()   # indicate state of the serial loop (flag set == serial loop not running)
        self.serial_loop_closed.set()       # will be cleared when serial reader loop is started
        self.serial_lock = RLock()          # used to ensure serial check is not executed while serial disconnect is in progress
        self.job_queue = Queue.Queue()      # contains jobs to be processed by the worker thread
        self.break_finished = Event()       # indicate state of break timer
        self.break_finished.set()           # Default state is set (not on break), this should be cleared 
                                            #     when the break timer (in break dialog) is running
        # Instantiate worker thread
        self.worker = Thread(target=self.__main_process, name='WorkerThread')
        self.worker.alive = True        # worker will loop while this variable is True
        self.worker.parse = True        # worker will process jobs only when this is True, else discards jobs
        self.worker.start()             # start worker thread
        
        # Create custom event
        self.BreakEvent, self.EVT_BREAK = wx.lib.newevent.NewEvent()
        self.Bind(self.EVT_BREAK, self._start_break)
        return 1

    def __main_process(self):
        ''' Repeatedly calls the job_queue.get() method. '''
        # Block if the queue is empty (wait until a  job is added then grab it) 
        while self.worker.alive:
            try:
                job = self.job_queue.get(block=True)
                if self.worker.parse:
                    if job == None:     # indicates that the program is closing down
                        pass
                    else:
                        try:
                            job()

                        except:
                            print traceback.format_exc()
                    
                else:   # discard jobs
                    pass

            except:
                print traceback.format_exc()
        
    def _start_break(self, event):
        ''' Create a breakdialog window and start the break counter'''
        
        print ">> Break started."
        try:
            self.break_frame = BreakDialog(self._on_end_break, self.cfg._break_len)
        except:
            print traceback.format_exc()

    def _on_end_break(self):
        # When the timer hits the set break time, end break
        print ">> Break ended."
        self.break_finished.set()
        self.sess_status.on_break = False
        try:
            self.console.flush()
        except:
            print traceback.format_exc()
        self.sess_status.alive = True
        self.plot.SetFocus()
        
    def status_msg(self, msg):
        # Updates the status bar of the main frame
        self.main_frame.status_message(msg)

    def review_session(self):
        ''' Called by the handler for the View Session button on the main menu'''
        self.status.review_frame = True
        self.rhdls = ReviewHandlers(self)   # Event handlers for reviewing previous data
        self.view_plot = ViewPlot(self)     # Used to review the previous sessions
        self.view_plot.Show()

    def setup_session(self):
        ''' Called by the handler for the Start Session button on the main menu'''
        self.sess_status = SessionState()   # state information for the current session
            
        # Disable automatic garbage collection. 
        # Garbage will be collected manually in between trials.
        gc.disable()
        gc.collect()

        # Reset initial Y axis maximum
        self.cfg._y_axis_max = self.cfg.Y_AXIS_MAX_INIT
        # Create a database.Session object to store data for the session
        self.sess_lock.acquire()    # ACQUIRE SESS_LOCK <--
        self.status.session = database.Session( config=self.cfg,
                                                pid=self.status.patient.id,
                                                stats=None)
        self.sess_lock.release()    # RELEASE SESS_LOCK -->
        # Create a windows for control panel and plot
        self.phdls = PlotHandlers(self)     # event handlers for control panel
        self.ctrl = ControlPanel(self)      # control panel GUI
        self.plot = LivePlot(self)          # plotting class and GUI
        self.ctrl.Bind(wx.EVT_CLOSE, self.phdls.exit_plot)
        self.plot.Bind(wx.EVT_CLOSE, self.phdls.exit_plot)
        self.status.ctrl_frame = True
        self.status.plot_frame = True
        self.pat_lock.acquire()     # ACQUIRE PAT_LOCK <--
        title = ' training' + ' - ' + self.status.patient.name + '-' + self.status.patient.id
        self.pat_lock.release()     # RELEASE PAT_LOCK -->
        self.plot.SetTitle(title)
        self.main_frame.Hide()
        self.plot.Show()
        self.ctrl.Show()

    def end_session(self):
        ''' Housekeeping at the end of the session, all trials completed'''
        self.worker.parse = False           # discard remaining jobs in queue
        dlg = wx.MessageDialog(None, "Session finished!", "Session complete", wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        sys.stdout.write('>>> End Session\n')
        try:
            self.phdls.save_sess(None)
        except:
            sys.stdout.write(traceback.format_exc())
            
        self.cfg._y_axis_max =  self.cfg.Y_AXIS_MAX_INIT             # reset plot y axis max for the next session
        self.phdls.serial_disconnect(None)      # disconnect serial port
        self.serial_loop_closed.wait()       # wait for serial loop to finish before destroying plot object in restore_mainmenu()
        self.status.session = None      # reset reference to current session object
        self.sess_status = None         # reset reference to current session state object
        self.restore_mainmenu()
        
    def restore_mainmenu(self):
        ''' Restores the main menu. 
            Called at the end of the handler for <Main Menu> button in the control panel'''
        # Enable automatic garbage collection. 
        gc.enable()
        try:
            
            if (self.status.plot_frame is True):
                self.plot.Destroy()                     # close plot frame
                self.plot = None                        # set plot frame pointer to None
                self.status.plot_frame = False          # set plot_frame status to False 
                self.ctrl.Destroy()                     # close control panel
                self.ctrl = None                        # set control panel pointer to None
                self.status.ctrl_frame = False          # set ctrl_frame status to False
                
            if (self.status.review_frame is True):   
                self.view_plot.Destroy()                     # close plot frame
                self.view_plot = None                        # set plot frame pointer to None
                self.status.review_frame = False
            
            
            
            self.main_frame.update_patient_panel()  # update to show the completed session
            self.status_msg('Main menu')
            self.main_frame.Show()
            self.main_frame.SetFocus()
        except:
            print traceback.format_exc()
        
    def on_exit(self, event):
        ''' Shut down the application.
            - Check that the current patient's data is saved
            - Ensure all threads are finished
            - Destroy all windows'''
        
        # Checks to see if the results have been saved, and offers and option to save if false
        if self.status.patient == None:
            pass
        elif self.status.patient_changed:
            dialog_text = "Save patient file?"
            dial = wx.MessageDialog(None, dialog_text, 'Question',
                                    wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
            ret = dial.ShowModal()
            if ret == wx.ID_YES:
                self.hdls.save_patient(None)
        
        # Kill all processes
        self.worker.alive = False
        self.job_queue.put(None)
        self.data_lock.acquire()
        self.main_frame.Destroy()
        self.data_lock.release()
        print '==========================================================='
        print '    Main application closed at: ', wx.DateTime.Now().Format()
        print '==========================================================='
        
# end of class MainApp

if __name__ == "__main__":
    MyApp = MainApp(0)
    MyApp.MainLoop()

# End of mainapp.py